package p20_mvc初体验;

import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.core.MethodParameter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 控制器方法的参数，是由参数解析器解析并赋值的
 * 参数解析器的个数很多，在解析一个参数时，会从参数解析器组中选一个最合适的
 * 自定义参数解析器步骤：
 * 1.实现HandlerMethodArgumentResolver，自定义参数解析规则
 * 2.添加到RequestMappingHandlerAdapter
 */
public class _3_参数解析器 {
    public static void main(String[] args) throws Exception {
        AnnotationConfigServletWebServerApplicationContext context =
                new AnnotationConfigServletWebServerApplicationContext(_1_DispatcherServlet初始化时机.class);
        RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class);
        //这个处理器适配器添加了自定义参数解析器
        RequestMappingHandlerAdapter handlerAdapter = context.getBean(RequestMappingHandlerAdapter.class);
        //发送请求，发现自定义参数解析器被执行了
        MockHttpServletRequest req = new MockHttpServletRequest("POST", "/remoteIpTest");
        req.addHeader("remote", "192.168.1.1");
        MockHttpServletResponse response = new MockHttpServletResponse();
        HandlerExecutionChain chain = handlerMapping.getHandler(req);
        handlerAdapter.handle(req, response, chain.getHandler());

        //得到所有参数解析器，由于目标方法最终由处理器适配器来调用，所以参数解析器组由处理器适配器持有
//        System.out.println("==============>打印所有参数解析器");
//        for (HandlerMethodArgumentResolver argumentResolver : handlerAdapter.getArgumentResolvers()) {
//            System.out.println(argumentResolver);
//        }

//        System.out.println("==============>返回值解析器");
//        //得到所有返回值解析器，由于目标方法最终由处理器适配器来调用并返回结果，所以返回值解析器由处理器适配器持有
//        for (HandlerMethodReturnValueHandler returnValueHandler : handlerAdapter.getReturnValueHandlers()) {
//            System.out.println(returnValueHandler);
//        }

        context.close();
    }

    /**
     * 自定义参数解析器
     * 想让该参数解析器生效，得手动加入到处理器适配器中作为其一个成员属性
     * HandlerMethodArgumentResolver -> HandlerMethod的参数解析器
     */
    static class RemoteIpArgResolver implements HandlerMethodArgumentResolver {
        /**
         * 判断该参数解析器支不支持目标控制器的参数，会遍历控制器方法的所有参数，一个一个调用该方法判断
         * 难道一次请求，会调用 参数解析器个数 * 目标控制器方法参数个数 次该方法?
         *
         * @param parameter 当前控制器方法遍历到的参数
         */
        @Override
        public boolean supportsParameter(MethodParameter parameter) {
            //下面逻辑是：如果该参数加了@RemoteIp，该参数解析器就可以处理该参数
            RemoteIp remoteIp = parameter.getParameterAnnotation(RemoteIp.class);
            System.out.println("<=======>自定义参数解析器被调用了:" + parameter.getParameterName());
            return remoteIp != null;
        }

        /**
         * 如果supportsParameter为true，则调用该方法
         * 该方法的返回值会被注入到当前的方法参数
         */
        @Override
        public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
            //假设请求源的ip放在remote请求头中
            return webRequest.getHeader("remote");
        }
    }

    /**
     * 自定义注解，供自定义参数解析器处理
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.PARAMETER})
    @interface RemoteIp {
    }
}
